package com.platform.core.service.impl;

import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.platform.comm.constants.GlobalConstant;
import com.platform.comm.dto.DtoMapper;
import com.platform.comm.exceptions.RestApiException;
import com.platform.comm.util.CommUtil;
import com.platform.core.entity.QSysFunction;
import com.platform.core.entity.QSysRole;
import com.platform.core.entity.QSysRoleFunction;
import com.platform.core.entity.SysFunction;
import com.platform.core.repository.SysFunctionRepository;
import com.platform.core.security.auth.AuthenticationFacade;
import com.platform.core.service.SysFunctionService;
import com.platform.core.service.SysRoleFunctionService;
import com.platform.dto.FunctionQo;
import com.platform.dto.FunctionTreeNode;
import com.platform.dto.MenuTree;
import com.platform.enums.FunctionStatus;
import com.platform.enums.FunctionType;
import com.platform.enums.RoleEnum;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.Predicate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.querydsl.QSort;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;

import java.time.LocalDateTime;
import java.util.*;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
 * Description: 菜单-Service实现类
 *
 * @author wxw Created on 2019/7/17
 **/
@Slf4j
@Service
@Transactional(readOnly = true, rollbackFor = Exception.class)
public class SysFunctionServiceImpl extends BaseEntityBizImpl<SysFunctionRepository, SysFunction> implements SysFunctionService {

    private final QSysFunction qSysFunction = QSysFunction.sysFunction;
    private final QSysRoleFunction qSysRoleFunction = QSysRoleFunction.sysRoleFunction;
    private final QSysRole qSysRole = QSysRole.sysRole;
    private final DtoMapper dtoMapper;
    private final SysRoleFunctionService sysRoleFunctionService;

    private final JdbcTemplate jdbcTemplate;

    public SysFunctionServiceImpl(SysFunctionRepository sysFunctionRepository, DtoMapper dtoMapper,
                                  SysRoleFunctionService sysRoleFunctionService, JdbcTemplate jdbcTemplate) {
        super(sysFunctionRepository);
        this.dtoMapper = dtoMapper;
        this.sysRoleFunctionService = sysRoleFunctionService;
        this.jdbcTemplate = jdbcTemplate;
    }


    @Override
    public Page<FunctionQo> queryFunctions(Predicate predicate, Pageable pageable, MultiValueMap<String, String> parameters) {
        if (pageable.getSort().isUnsorted()) {
            pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), QSort.by(qSysFunction.sortNum.asc()));
        }
        BooleanBuilder builder = new BooleanBuilder(predicate);
        builder.and(qSysFunction.type.eq(FunctionType.FUN));
        String menuName = parameters.getFirst("menuName");
        if (StringUtils.isNotBlank(menuName)) {
            builder.and(qSysFunction.name.like("%" + menuName + "%"));
        }
        String parentId = parameters.getFirst("parentId");
        if (StringUtils.isNotBlank(parentId)) {
            builder.and(qSysFunction.parentId.eq(parentId));
        }

        Page<SysFunction> page = queryByPage(builderConditions(builder, parameters, SysFunction.class, qSysFunction.getMetadata()), pageable);
        return page.map(this::change);
    }

    @Override
    public List<FunctionQo> queryAllChildren(String id) {
        List<Tuple> iterable = repository.findAll(qSysFunction.parentId.eq(id).and(qSysFunction.delFlag.eq(FunctionStatus.Y)),
                Sort.by("sortNum"), qSysFunction.id, qSysFunction.type, qSysFunction.name, qSysFunction.url, qSysFunction.delFlag);
        return iterable.stream().map(entity -> dtoMapper.map(entity, FunctionQo.class)).collect(Collectors.toList());
    }

    @Caching(
            evict = {
                    @CacheEvict(value = GlobalConstant.BASE_REDIS_CACHE_PREFIX + "function", key = "'tree'", allEntries = true),
                    @CacheEvict(value = GlobalConstant.BASE_REDIS_CACHE_PREFIX + "roleFunctions", allEntries = true),
                    @CacheEvict(value = GlobalConstant.BASE_REDIS_CACHE_PREFIX + "roleAdminFunctions", allEntries = true)
            }
    )
    @Transactional(rollbackFor = Exception.class)
    @Override
    public FunctionQo create(FunctionQo functionQo) {
        functionQo.init();
        if (checkMenuNameIsExist("", functionQo.getName(), functionQo.getParentId())) {
            throw new RestApiException("同父级菜单下菜单名称重复");
        }
        if (!checkSubSystemCodeCreate(functionQo)) {
            throw new RestApiException("子应用编码重复");
        }
        String id = CommUtil.uuid();
        SysFunction sysFunction = dtoMapper.map(functionQo, SysFunction.class);
        sysFunction.setId(id);
        // 设置route和level
        if (Strings.isNullOrEmpty(functionQo.getParentId())) {
            sysFunction.setLevel(1);
            sysFunction.setRoute(sysFunction.getId());
        } else {
            SysFunction parent = fetchById(functionQo.getParentId());
            sysFunction.setRoute(parent.getRoute() + ROUTE_SEPARATOR + sysFunction.getId());
            sysFunction.setLevel(parent.getLevel() + 1);
        }
        saveEntity(sysFunction);
        // 设置权限
        if (!CollectionUtils.isEmpty(functionQo.getPermissions())) {
            functionQo.getPermissions().forEach(qo -> {
                qo.setParentId(sysFunction.getId());
                create(qo);
            });
        }
        return dtoMapper.map(sysFunction, FunctionQo.class);
    }

    private boolean checkSubSystemCodeCreate(FunctionQo functionQo) {
        boolean flag = true;
        if (StringUtils.isNotBlank(functionQo.getSubSystemCode())) {
            SysFunction sysFunction = getOne(QSysFunction.sysFunction.subSystemCode.eq(functionQo.getSubSystemCode()).and(qSysFunction.delFlag.ne(FunctionStatus.D)));
            if (sysFunction != null) {
                flag = false;
            }
        }
        return flag;
    }

    @Caching(
            evict = {
                    @CacheEvict(value = GlobalConstant.BASE_REDIS_CACHE_PREFIX + "function", key = "'tree'", allEntries = true),
                    @CacheEvict(value = GlobalConstant.BASE_REDIS_CACHE_PREFIX + "roleFunctions", allEntries = true),
                    @CacheEvict(value = GlobalConstant.BASE_REDIS_CACHE_PREFIX + "roleAdminFunctions", allEntries = true)
            }
    )
    @Transactional(rollbackFor = Exception.class)
    @Override
    public FunctionQo modify(FunctionQo functionQo) {
        SysFunction sysFunction = fetchById(functionQo.getId());
        if (checkMenuNameIsExist(functionQo.getId(), functionQo.getName(), functionQo.getParentId())) {
            throw new RestApiException("同父级菜单下菜单名称重复");
        }
        if (!checkSubSystemCodeMod(functionQo)) {
            throw new RestApiException("子应用编码重复");
        }
        dtoMapper.map(functionQo, sysFunction);
        sysFunction.setUpdateDate(LocalDateTime.now());
        if (StringUtils.isEmpty(functionQo.getIcon())) {
            sysFunction.setIcon(null);
        }
        if (StringUtils.isEmpty(functionQo.getDesc())) {
            sysFunction.setDesc(null);
        }
        saveEntity(sysFunction);
        if (!CollectionUtils.isEmpty(functionQo.getPermissions())) {
            // 这里不能直接删除后重建，主要是因为关联角色
            functionQo.getPermissions().stream().filter(qo -> Strings.isNullOrEmpty(qo.getId())).forEach(qo -> {
                qo.setParentId(sysFunction.getId());
                create(qo);
            });
            functionQo.getPermissions().stream().filter(qo -> !Strings.isNullOrEmpty(qo.getId())).forEach(this::modify);
        }
        return dtoMapper.map(sysFunction, FunctionQo.class);
    }

    private boolean checkSubSystemCodeMod(FunctionQo functionQo) {
        boolean flag = true;
        if (StringUtils.isNotBlank(functionQo.getSubSystemCode())) {
            SysFunction sysFunction = getOne(QSysFunction.sysFunction.subSystemCode.eq(functionQo.getSubSystemCode())
                    .and(QSysFunction.sysFunction.id.ne(functionQo.getId())).and(qSysFunction.delFlag.ne(FunctionStatus.D)));
            if (sysFunction != null && !StringUtils.equals(sysFunction.getId(), functionQo.getId())) {
                flag = false;
            }
        }
        return flag;
    }

    @Caching(
            evict = {
                    @CacheEvict(value = GlobalConstant.BASE_REDIS_CACHE_PREFIX + "function", key = "'tree'", allEntries = true),
                    @CacheEvict(value = GlobalConstant.BASE_REDIS_CACHE_PREFIX + "roleFunctions", allEntries = true),
                    @CacheEvict(value = GlobalConstant.BASE_REDIS_CACHE_PREFIX + "roleAdminFunctions", allEntries = true)
            }
    )

    @Override
    public boolean delete(String id) {
        preDelete(id);
        if (repository.existsById(id)) {
            repository.deleteById(id);
        }
        return true;
    }

    @Caching(
            evict = {
                    @CacheEvict(value = GlobalConstant.BASE_REDIS_CACHE_PREFIX + "function", key = "'tree'", allEntries = true),
                    @CacheEvict(value = GlobalConstant.BASE_REDIS_CACHE_PREFIX + "roleFunctions", allEntries = true),
                    @CacheEvict(value = GlobalConstant.BASE_REDIS_CACHE_PREFIX + "roleAdminFunctions", allEntries = true)
            }
    )
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void remove(String id) {
        delete(id);
    }

    private void preDelete(String functionId) {
        // 物理删除时需要判断是否有子节点
        long count = repository.count(qSysFunction.parentId.eq(functionId).and(qSysFunction.type.ne(FunctionType.ACT)));
        if (count > 0) {
            throw new RestApiException("菜单包含子菜单，禁止删除");
        }
        // 删除权限
        Iterable<SysFunction> iterable = repository.findAll(qSysFunction.parentId.eq(functionId));

        StreamSupport.stream(iterable.spliterator(), false)
                .map(SysFunction::getId)
                .forEach(sysRoleFunctionService::removeByFuncId);
        // 删除关联权限
        sysRoleFunctionService.removeByFuncId(functionId);
        repository.deleteInBatch(iterable);

    }

    @Cacheable(value = GlobalConstant.BASE_REDIS_CACHE_PREFIX + "function", key = "'tree'", unless = "#result == null")
    @Override
    public FunctionTreeNode loadAllFunction() {
        Optional<SysFunction> optional = repository.findById(ROOT);
        return transToTreeNode(optional.orElseThrow(() -> new RestApiException("根菜单不存在")), this::loadSubNode, null);
    }

    @Override
    public FunctionTreeNode loadAllFunctionByApp(String appCode) {
        Assert.isTrue(!Strings.isNullOrEmpty(appCode), "应用编码不能为空");
        SysFunction appRootFunction = repository.findOne(qSysFunction.subSystemCode.eq(appCode).and(qSysFunction.delFlag.eq(FunctionStatus.Y))).orElse(null);
        if (appRootFunction == null) {
            return null;
        }
        return transToTreeNode(appRootFunction, this::loadAllSubNode, null);
    }

    @Override
    public FunctionTreeNode previewByApp(String appCode) {
        SysFunction appRootFunction = repository.findOne(qSysFunction.subSystemCode.eq(appCode).and(qSysFunction.delFlag.eq(FunctionStatus.Y))).orElse(null);
        if (appRootFunction == null) {
            return null;
        }
        Optional<SysFunction> optional = repository.findById(appRootFunction.getId());
        List<FunctionTreeNode> subNodes = optional.map(sysFunction -> {
            List<SysFunction> list = StreamSupport.stream(
                            repository.findAll(qSysFunction.route.startsWith(sysFunction.getRoute())
                                            .and(qSysFunction.delFlag.eq(FunctionStatus.Y)).and(qSysFunction.type.in(FunctionType.FUN, FunctionType.DIR)),
                                    QSort.by(qSysFunction.sortNum.asc())).spliterator(), true)
                    .filter(function -> StringUtils.isNotBlank(function.getParentId()))
                    .collect(Collectors.toList());
            return buildTreeNode(list.stream().map(this::toTreeNode).collect(Collectors.toList()), sysFunction.getId());
        }).orElseThrow(() -> new RestApiException("应用菜单不存在"));

        FunctionTreeNode appFunctionTreeNode = this.toTreeNode(appRootFunction);
        appFunctionTreeNode.setChildren(subNodes);
        return appFunctionTreeNode;
    }

    /**
     * 递归加载所有菜单子节点信息
     */
    private List<FunctionTreeNode> loadSubNode(String id, List<String> functionIds) {
        List<SysFunction> list = Lists.newArrayList(repository.findAll(qSysFunction.parentId.eq(id)
                .and(qSysFunction.type.in(FunctionType.FUN, FunctionType.DIR)), Sort.by("sortNum")));
        return list.stream()
                .map(sysFunction -> transToTreeNode(sysFunction, this::loadSubNode, functionIds)).collect(Collectors.toList());
    }

    /**
     * 递归加载所有菜单子节点信息
     */
    private List<FunctionTreeNode> loadAllSubNode(String id, List<String> functionIds) {
        List<SysFunction> list = Lists.newArrayList(repository.findAll(qSysFunction.parentId.eq(id)
                .and(qSysFunction.type.in(FunctionType.FUN, FunctionType.DIR)), Sort.by("sortNum")));
        return list.stream()
                .map(sysFunction -> transToTreeNode(sysFunction, this::loadAllSubNode, functionIds)).collect(Collectors.toList());
    }

    /**
     * 递归加载所有菜单子节点信息
     */
    private List<FunctionTreeNode> loadSubAllNode(String id, List<String> functionIds) {
        List<SysFunction> list = Lists.newArrayList(repository.findAll(qSysFunction.parentId.eq(id)
                .and(qSysFunction.delFlag.eq(FunctionStatus.Y)), Sort.by("sortNum")));
        return list.stream()
                .map(sysFunction -> transToTreeNode(sysFunction, this::loadSubAllNode, functionIds)).collect(Collectors.toList());
    }

    private FunctionTreeNode transToTreeNode(SysFunction sysFunction, BiFunction<String, List<String>,
            List<FunctionTreeNode>> loadChildren, List<String> functionIds) {
        FunctionTreeNode node = new FunctionTreeNode();
        node.setId(sysFunction.getId());
        node.setText(sysFunction.getName());
        node.setMenuType(sysFunction.getType().getValue());
        node.setPath(sysFunction.getPath());
        node.setUrl(sysFunction.getUrl());
        node.setDesc(sysFunction.getDesc());
        node.setIcon(sysFunction.getIcon());
        node.setBizType(sysFunction.getBizType());
        if (!CollectionUtils.isEmpty(functionIds)) {
            node.setEnabled(functionIds.contains(sysFunction.getId()));
        } else {
            node.setEnabled(false);
        }
        node.setChildren(loadChildren.apply(sysFunction.getId(), functionIds));
        return node;
    }

    @Override
    public FunctionQo queryWithPermissions(String id) {
        return queryById(id).map(this::change).orElseThrow(() -> new RestApiException("未查询到菜单详情"));
    }

    private FunctionQo change(SysFunction entity) {
        FunctionQo functionQo = dtoMapper.map(entity, FunctionQo.class);
        functionQo.setPermissions(StreamSupport.stream(repository.findAll(qSysFunction.parentId.eq(functionQo.getId())
                                .and(qSysFunction.delFlag.eq(FunctionStatus.Y)).and(qSysFunction.type.eq(FunctionType.ACT)),
                        QSort.by(qSysFunction.sortNum.asc())).spliterator(), true)
                .map(child -> dtoMapper.map(child, FunctionQo.class)).collect(Collectors.toList()));
        return functionQo;
    }

    @Override
    public FunctionTreeNode treeAll(String roleId) {
        Optional<SysFunction> optional = repository.findById(ROOT);
        List<String> functionIds = sysRoleFunctionService.findFunctionsByRole(roleId);
        return transToTreeNode(optional.orElseThrow(() -> new RestApiException("根菜单不存在")), this::loadSubAllNode, functionIds);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void changeFlag(List<String> ids, FunctionStatus functionStatus) {
        ids.stream().map(this::fetchById).forEach(sysFunction -> sysFunction.setDelFlag(functionStatus));
        repository.flush();
    }

    @Override
    public long searchCount(Map<String, String> param) {
        String appCode = param.get("appCode");
        String viewKey = param.get("viewKey");

        if (StringUtils.isEmpty(appCode) || StringUtils.isEmpty(viewKey)) {
            return 0L;
        }

        BooleanBuilder booleanBuilder = new BooleanBuilder();
        booleanBuilder.and(qSysFunction.delFlag.eq(FunctionStatus.Y)
                .and(qSysFunction.path.contains(viewKey))
                .and(qSysFunction.bizType.isNotNull()));
        return this.count(booleanBuilder);
    }

    @Override
    public boolean checkMenuNameIsExist(String id, String menuName, String parentId) {
        BooleanBuilder builder = new BooleanBuilder();
        if (StringUtils.isNotBlank(id)) {
            builder.and(qSysFunction.id.ne(id));
        }
        if (StringUtils.isNotBlank(parentId)) {
            builder.and(qSysFunction.parentId.eq(parentId));
        } else {
            return false;
        }
        builder.and(qSysFunction.name.eq(menuName)).and(qSysFunction.delFlag.eq(FunctionStatus.Y)).and(qSysFunction.type.ne(FunctionType.ACT));
        return count(builder) > 0;
    }

    @Override
    public List<MenuTree> treeByAuthorizes(Map<String, Boolean> param) {
        // 1. 获取用户权限
        Set<String> authorizes =
                org.springframework.util.StringUtils.commaDelimitedListToSet(AuthenticationFacade.getUser().getAuth());
        List<MenuTree> menuTrees;
        if (authorizes.contains(RoleEnum.ROLE_ADMIN.name())) {
            // 管理员权限，拉去所有菜单
            menuTrees = treeByAdmin();
        } else {
            log.debug("其它角色,", JSON.toJSONString(authorizes));
            // 其他角色，根据权限配置获取菜单
            menuTrees = treeByRoles(authorizes);
            log.debug("menuTrees size=" + menuTrees.size());
            menuTrees.forEach(item -> {
                log.debug("menu tree name=" + item.getName());
            });
        }
        List<MenuTree> result = new ArrayList<>();
        if (MapUtil.isNotEmpty(param) && !CollectionUtils.isEmpty(menuTrees)) {
            Set<String> subSystemCodes = param.keySet();
            for (MenuTree x : menuTrees) {
                if (subSystemCodes.contains(x.getSubSystemCode())) {
                    if (param.getOrDefault(x.getSubSystemCode(), false)) {
                        result.addAll(x.getChildren());
                    } else {
                        result.add(x);
                    }
                }

                // 平台子应用菜单加载
                if ("pre_ncloud_sys_function_20000".equals(x.getId())) {
                    if (Objects.nonNull(x.getChildren())) {
                        for (MenuTree subApp : x.getChildren()) {
                            if (subSystemCodes.contains(subApp.getSubSystemCode())) {
                                if (param.getOrDefault(subApp.getSubSystemCode(), false)) {
                                    result.addAll(subApp.getChildren());
                                } else {
                                    result.add(subApp);
                                }
                            }
                        }
                    }
                }
            }
        } else {
            result = menuTrees;
        }
        log.debug("最终的菜单树", JSON.toJSONString(result));
        return result;
    }

    @Override
    public List<MenuTree> treeByAuthorizesLowcode(Map<String, String> param) {
        String platformCode = param.get("code");
        if (StringUtils.isEmpty(platformCode)) {
            platformCode = "init_code_first";
        }

        // 1. 获取用户权限
        Set<String> authorizes =
                org.springframework.util.StringUtils.commaDelimitedListToSet(AuthenticationFacade.getUser().getAuth());
        List<MenuTree> menuTrees;
        if (authorizes.contains(RoleEnum.ROLE_ADMIN.name())) {
            // 管理员权限，拉去所有菜单
            menuTrees = treeByAdminByLowcode(platformCode);
        } else {
            // 其他角色，根据权限配置获取菜单
            menuTrees = treeByRolesLowcode(authorizes, platformCode);
        }

        return menuTrees;
    }

    @Override
    public FunctionQo queryBySubSystemCode(String subSystemCode) {
        Assert.hasText(subSystemCode, "subSystemCode不能为空");
        return this.queryOne(QSysFunction.sysFunction.subSystemCode.eq(subSystemCode))
                .map(entity -> dtoMapper.map(entity, FunctionQo.class)).orElse(null);
    }

    @Override
    public FunctionQo queryBySubSystemId(String subSystemId) {
        Assert.hasText(subSystemId, "subSystemId不能为空");
        return this.queryOne(QSysFunction.sysFunction.subSystemId.eq(subSystemId))
                .map(entity -> dtoMapper.map(entity, FunctionQo.class)).orElse(null);
    }

    /**
     * 管理员菜单树
     *
     * @return []
     */
    private List<MenuTree> treeByAdmin() {
        // 1. 获取系统根菜单
        Optional<SysFunction> optional = repository.findById(ROOT);
        // 2. 获取所有各类应用根菜单
        Set<String> appIdSet = StreamSupport.stream(repository.findAll(qSysFunction.subSystemCode.isNotNull()
                        .and(qSysFunction.bizType.equalsIgnoreCase("module"))).spliterator(), false)
                .map(entity -> entity.getId()).collect(Collectors.toSet());
        return optional.map(sysFunction -> {
            List<SysFunction> list = StreamSupport.stream(
                            repository.findAll(qSysFunction.route.startsWith(sysFunction.getId())
                                            .and(qSysFunction.delFlag.eq(FunctionStatus.Y)).and(qSysFunction.type.in(FunctionType.FUN, FunctionType.DIR)),
                                    QSort.by(qSysFunction.sortNum.asc())).spliterator(), true)
                    .filter(function -> StringUtils.isNotBlank(function.getParentId()))
                    .filter(function -> !appIdSet.contains(function.getId()))
                    .collect(Collectors.toList());

            return build(list.stream().map(this::toMenuTree).collect(Collectors.toList()), sysFunction.getId());
        }).orElseThrow(() -> new RestApiException("根菜单不存在"));

    }

    private List<MenuTree> treeByAdminByLowcode(String code) {
        // 1. 获取系统根菜单
        Optional<SysFunction> optional = repository.findById(SysFunctionService.ROOT);
        // 2. 获取子应用根菜单
        Set<SysFunction> appSet = StreamSupport.stream(repository.findAll(qSysFunction.subSystemCode.eq(code)
                .and(qSysFunction.bizType.equalsIgnoreCase("module"))).spliterator(), false).collect(Collectors.toSet());
        //3. 获取系统管理菜单
        String sysManageId = repository.findOne(qSysFunction.name.eq("系统管理").and(qSysFunction.subSystemCode.eq("SYSTEM_MANAGE")))
                .map(SysFunction::getId).orElse("null");


        if (appSet.size() > 1) {
            throw new RestApiException("查询到多组应用菜单");
        }

        String appId = appSet.stream().map(SysFunction::getId).findFirst().orElse("app_id_init");

        List<MenuTree> totalMenuTree = Lists.newArrayList();
        return optional.map(sysFunction -> {
            BooleanBuilder builder = new BooleanBuilder();
            List<SysFunction> list = StreamSupport.stream(
                            repository.findAll(builder.and(qSysFunction.route.like("%" + SysFunctionService.LOWROOT + "%").
                                                    or(qSysFunction.route.like("%" + appId + "%")).
                                                    or(qSysFunction.id.eq(SysFunctionService.LOWSUBROOT)).
                                                    or(qSysFunction.route.like("%" + sysManageId + "%"))
                                            )
                                            .and(qSysFunction.delFlag.eq(FunctionStatus.Y)).and(qSysFunction.type.in(FunctionType.FUN, FunctionType.DIR)),
                                    QSort.by(qSysFunction.sortNum.asc())).spliterator(), true)
                    .filter(function -> StringUtils.isNotBlank(function.getParentId()))
                    .collect(Collectors.toList());

            List<MenuTree> allMenuTree = build(list.stream().map(this::toMenuTree).collect(Collectors.toList()), sysFunction.getId());
            MenuTree appMenuTree = allMenuTree.stream().filter(menuTree -> menuTree.getId().equalsIgnoreCase(SysFunctionService.LOWSUBROOT)).findFirst().orElse(null);
            if (Objects.nonNull(appMenuTree)) {
                allMenuTree.remove(appMenuTree);
                if (Objects.nonNull(appMenuTree.getChildren())) {
                    totalMenuTree.addAll(appMenuTree.getChildren());
                }
            }
            totalMenuTree.addAll(allMenuTree);
            return totalMenuTree;
        }).orElseThrow(() -> new RestApiException("根菜单不存在"));

    }

    /**
     * 转换数据
     *
     * @param sysFunction 菜单
     * @return 菜单
     */
    private MenuTree toMenuTree(SysFunction sysFunction) {
        MenuTree node = new MenuTree();
        node.setId(sysFunction.getId());
        node.setParentId(sysFunction.getParentId());
        node.setName(sysFunction.getPath());
        node.setTitle(sysFunction.getName());
        node.setIcon(sysFunction.getIcon());
        node.setSort(sysFunction.getSortNum());
        node.setDesc(sysFunction.getDesc());
        node.setSubSystemCode(sysFunction.getSubSystemCode());
        node.setUrl(sysFunction.getUrl());
        node.setLinkType(Optional.ofNullable(sysFunction.getLinkType()).orElse("route"));
        return node;
    }

    /**
     * 转换菜单
     *
     * @param sysFunction 菜单
     * @return 菜单
     */
    private FunctionTreeNode toTreeNode(SysFunction sysFunction) {
        FunctionTreeNode node = new FunctionTreeNode();
        node.setId(sysFunction.getId());
        node.setText(sysFunction.getName());
        node.setParentId(sysFunction.getParentId());
        node.setMenuType(sysFunction.getType().getValue());
        node.setPath(sysFunction.getPath());
        node.setUrl(sysFunction.getUrl());
        node.setDesc(sysFunction.getDesc());
        node.setIcon(sysFunction.getIcon());
        node.setBizType(sysFunction.getBizType());
        node.setSubSystemCode(sysFunction.getSubSystemCode());
        node.setLinkType(Optional.ofNullable(sysFunction.getLinkType()).orElse("route"));
        return node;
    }

    /**
     * 普通角色菜单树
     *
     * @param authorizes 当前用户登录角色
     * @return []
     */
    private List<MenuTree> treeByRoles(Set<String> authorizes) {
        // 1. 获取根菜单
        Optional<SysFunction> optional = repository.findById(ROOT);
        return optional.map(sysFunction -> {
            BooleanBuilder builder = new BooleanBuilder(qSysFunction.route.startsWith(sysFunction.getId())
                    .and(qSysFunction.delFlag.eq(FunctionStatus.Y)));
            builder.and(qSysRole.code.in(authorizes));
            List<SysFunction> list = repository.findAll(builder,
                            jpqlQuery -> jpqlQuery.leftJoin(qSysRoleFunction).on(qSysFunction.id.eq(qSysRoleFunction.functionId))
                                    .leftJoin(qSysRole).on(qSysRole.id.eq(qSysRoleFunction.roleId))
                                    .orderBy(qSysFunction.level.asc(), qSysFunction.sortNum.asc()), qSysFunction.route)
                    .stream()
                    .map(tuple -> tuple.get(qSysFunction.route))
                    .filter(StringUtils::isNotBlank)
                    .map(route -> StringUtils.split(route, ROUTE_SEPARATOR))
                    .map(array -> repository.findAll(qSysFunction.id.in(array).and(qSysFunction.delFlag.eq(FunctionStatus.Y))))
                    .flatMap(iterable -> StreamSupport.stream(iterable.spliterator(), true))
                    .distinct()
                    .filter(function -> StringUtils.isNotBlank(function.getParentId()) && !FunctionType.ACT.equals(function.getType()))
                    .collect(Collectors.toList());
            return build(list.stream().map(this::toMenuTree).collect(Collectors.toList()), sysFunction.getId());
        }).orElseThrow(() -> new RestApiException("根菜单不存在"));
    }

    private List<MenuTree> treeByRolesLowcode(Set<String> authorizes, String code) {
        // 1. 获取根菜单
        Optional<SysFunction> optional = repository.findById(ROOT);
        Set<String> appIdSet = StreamSupport.stream(repository.findAll(qSysFunction.subSystemCode.eq(code)
                        .and(qSysFunction.bizType.equalsIgnoreCase("module"))).spliterator(), false)
                .map(entity -> entity.getId()).collect(Collectors.toSet());

        if (appIdSet.size() > 1) {
            throw new RestApiException("查询到多组应用菜单");
        }

        String appId = appIdSet.stream().findFirst().orElse("app_id_init");

        List<MenuTree> totalMenuTree = Lists.newArrayList();
        return optional.map(sysFunction -> {
            BooleanBuilder builder = new BooleanBuilder(qSysFunction.route.like("%" + appId + "%")
                    .and(qSysFunction.delFlag.eq(FunctionStatus.Y)));
            builder.and(qSysRole.code.in(authorizes));
            List<SysFunction> list = repository.findAll(builder,
                            jpqlQuery -> jpqlQuery.leftJoin(qSysRoleFunction).on(qSysFunction.id.eq(qSysRoleFunction.functionId))
                                    .leftJoin(qSysRole).on(qSysRole.id.eq(qSysRoleFunction.roleId)), qSysFunction.route)
                    .stream()
                    .map(tuple -> tuple.get(qSysFunction.route))
                    .filter(StringUtils::isNotBlank)
                    .map(route -> StringUtils.split(route, ROUTE_SEPARATOR))
                    .map(array -> repository.findAll(qSysFunction.id.in(array).and(qSysFunction.delFlag.eq(FunctionStatus.Y))))
                    .flatMap(iterable -> StreamSupport.stream(iterable.spliterator(), true))
                    .distinct()
                    .filter(function -> StringUtils.isNotBlank(function.getParentId()) && !FunctionType.ACT.equals(function.getType()))
                    .sorted(Comparator.comparing(SysFunction::getSortNum))
                    .collect(Collectors.toList());
            List<MenuTree> allMenuTree = build(list.stream().map(this::toMenuTree).collect(Collectors.toList()), sysFunction.getId());
            MenuTree appMenuTree = allMenuTree.stream().filter(menuTree -> menuTree.getId().equalsIgnoreCase(SysFunctionService.LOWSUBROOT)).findFirst().orElse(null);
            if (Objects.nonNull(appMenuTree)) {
                allMenuTree.remove(appMenuTree);
                if (Objects.nonNull(appMenuTree.getChildren())) {
                    totalMenuTree.addAll(appMenuTree.getChildren());
                }
            }
            totalMenuTree.addAll(allMenuTree);
            return totalMenuTree;
        }).orElseThrow(() -> new RestApiException("根菜单不存在"));
    }


    /**
     * 两层循环实现建树
     *
     * @param treeNodes 传入的树节点列表
     * @return 树
     */
    private List<MenuTree> build(List<MenuTree> treeNodes, Object root) {
        List<MenuTree> trees = new ArrayList<>();

        for (MenuTree treeNode : treeNodes) {
            if (root.equals(treeNode.getParentId())) {
                trees.add(treeNode);
            }
            for (MenuTree it : treeNodes) {
                if (it.getParentId().equals(treeNode.getId())) {
                    if (treeNode.getChildren() == null) {
                        treeNode.setChildren(new ArrayList<>());
                    }
                    treeNode.add(it);
                }
            }
        }
        return trees;
    }

    private List<FunctionTreeNode> buildTreeNode(List<FunctionTreeNode> treeNodes, Object root) {
        List<FunctionTreeNode> trees = new ArrayList<>();
        for (FunctionTreeNode treeNode : treeNodes) {
            if (root.equals(treeNode.getParentId())) {
                trees.add(treeNode);
            }
            for (FunctionTreeNode it : treeNodes) {
                if (it.getParentId().equals(treeNode.getId())) {
                    if (treeNode.getChildren() == null) {
                        treeNode.setChildren(new ArrayList<>());
                    }
                    treeNode.getChildren().add(it);
                }
            }
        }
        return trees;
    }

    @Override
    public boolean isUsedByFuncPath(Map<String, String> parametersMap) {
        String funcPath = parametersMap.get("funcPath");
        Preconditions.checkArgument(!Strings.isNullOrEmpty(funcPath), "funcPath不能为空");
        String bizType = parametersMap.getOrDefault("bizType", "");
        BooleanBuilder builder = new BooleanBuilder();

        builder.and(qSysFunction.delFlag.eq(FunctionStatus.Y));
        builder.and(qSysFunction.path.contains(funcPath));
        if (!Strings.isNullOrEmpty(bizType)) {
            builder.and(qSysFunction.bizType.eq(bizType));
        }
        return count(builder) > 0 ? Boolean.TRUE : Boolean.FALSE;
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public void removeByRoute(FunctionQo functionQo) {
        String route = functionQo.getRoute();
        if (!Strings.isNullOrEmpty(route)) {
            BooleanBuilder builder = new BooleanBuilder();
            builder.and(qSysFunction.route.like(route + "%"));
            // 删除权限
            Iterable<SysFunction> iterable = repository.findAll(builder);
            repository.deleteInBatch(iterable);
        }
    }

    /**
     * 根据条件查询菜单信息集合，返回list
     *
     * @param parametersMap
     * @return 菜单树
     */
    @Override
    public List<FunctionQo> searchFunctionByCodeAndBizType(Map<String, String> parametersMap) {
        //根据应用code 获得应用下默认顶级菜单 ，然后查找route 包含顶级菜单id的菜单信息
        SysFunction sysFunction = getOne(QSysFunction.sysFunction.subSystemCode.eq(parametersMap.get("code")).and(qSysFunction.delFlag.ne(FunctionStatus.D)));
        if (sysFunction == null) {
            return new ArrayList<>();
        }

        //获取应用下所有一级菜单 bizType=parametersMap.get("bizType")
        List<SysFunction> functions = Lists.newArrayList(repository.findAll(qSysFunction.parentId.eq(sysFunction.getId())
                        .and(qSysFunction.delFlag.eq(FunctionStatus.Y)).and(qSysFunction.bizType.eq(parametersMap.get("bizType"))),
                QSort.by(qSysFunction.sortNum.asc())));
        if (functions == null || functions.size() <= 0) {
            return new ArrayList<>();
        }
        //获取符合条件的一级菜单下所有子菜单信息
        List<FunctionQo> allList = Lists.newArrayList();
        functions.stream().forEach(function -> {
            List<FunctionQo> functionQos = StreamSupport.stream(
                            repository.findAll(qSysFunction.route.contains(function.getId())
                                            .and(qSysFunction.delFlag.eq(FunctionStatus.Y)),
                                    QSort.by(qSysFunction.sortNum.asc())).spliterator(), true)
                    .map(entity -> dtoMapper.map(entity, FunctionQo.class)).collect(Collectors.toList());
            allList.addAll(functionQos);
        });
        return allList;
    }

    @Override
    @Transactional
    public void initRouteCode() {
        String sql = "update ncloud_sys_function set route_code = '%s' where id = '%s'";
        List<SysFunction> lists = queryAll(new BooleanBuilder());
        lists.stream().forEach(item -> {
            String ids = doInit(item);
            jdbcTemplate.update(String.format(sql, ids, item.getId()));
        });
    }

    private String doInit(SysFunction sysFunction) {
        String ids = sysFunction.getId();
        if (!Strings.isNullOrEmpty(sysFunction.getParentId())) {
            ids = doInit(sysFunction.getParentId(), ids);
        }
        return ids;
    }

    private String doInit(String id, String ids) {
        Optional<SysFunction> optional = queryById(id);
        if (!optional.isPresent()) {
            return ids;
        }
        SysFunction menu = optional.get();
        ids = menu.getId() + ";" + ids;
        if (!Strings.isNullOrEmpty(menu.getParentId())) {
            return doInit(menu.getParentId(), ids);
        }
        return ids;
    }


}
